#include "chunked_buffer.h"
#include "../util/assert.h"
#include <vector>
#include <iterator>

namespace wilson::http
{
    chunked_buffer::~chunked_buffer()
    {
        for (auto arr_p : m_blks)
            delete[] arr_p.data();
    }

    ::std::span<::std::byte> 
    chunked_buffer::
    next_write_block(size_t nbytes)
    {
        WILSON_ASSERT(nbytes);
        ::std::span buffer{ new ::std::byte[nbytes], nbytes };
        return m_blks.emplace_back(buffer);
    }

    ::std::span<const ::std::byte> 
    chunked_buffer::
    read_block(size_t idx) const
    {
        return m_blks[idx];
    }

    ::std::span<::std::byte> 
    chunked_buffer::
    get_writeable_block(size_t idx)
    {
        return m_blks[idx];
    }

    size_t chunked_buffer::size() const noexcept
    {
        size_t result{};
        for (const auto& blk : m_blks)
            result += blk.size();
        return result;
    }

    void chunked_buffer::merge_to_unique()
    {
        chunked_buffer result{ m_default_blk_size };
        auto buffer = result.next_write_block(size() + 5); // avoid SEG
        auto integral_view = as_integral();
        ::std::copy(integral_view.begin(), integral_view.end(), buffer.begin());
        ::std::swap(m_blks, result.m_blks);
    }

    chunked_buffer_as_integral_iterator::
    chunked_buffer_as_integral_iterator(
        const chunked_buffer_as_integral& parent, 
        size_t curblk, 
        size_t curbyte
    ) noexcept
        : m_cur_blk{ curblk }, 
          m_cur_byte{ curbyte }, 
          m_parent{ &parent }
    {
    }

    chunked_buffer_as_integral_iterator& 
    chunked_buffer_as_integral_iterator::
    operator++() noexcept
    {
        return operator+=(1);
    }

    chunked_buffer_as_integral_iterator  
    chunked_buffer_as_integral_iterator::
    operator++(int) noexcept
    {
        chunked_buffer_as_integral_iterator ret{ *this };
        ++ret;
        return ret;
    }

    chunked_buffer_as_integral_iterator& 
    chunked_buffer_as_integral_iterator::
    operator+=(difference_type val) noexcept
    {
        if (val == 0) return *this;
        else if (val < 0) return operator-=(-val);

        auto blk = m_parent->m_cb.read_block(m_cur_blk);

        // reach the end of current `blk`, and shift to next block
        while (m_cur_byte + val >= blk.size())
        {
            val -= blk.size() - m_cur_byte;
            ++m_cur_blk;
                
            /* Make the `blk` refer to next block
             * and reset `m_cur_byte`
             */
            blk = m_parent->m_cb.read_block(m_cur_blk);
            m_cur_byte = 0;
        }
        m_cur_byte += val;

        return *this;
    }

    chunked_buffer_as_integral_iterator  
    chunked_buffer_as_integral_iterator::
    operator+(difference_type val) const noexcept
    {
        chunked_buffer_as_integral_iterator ret{ *this };
        ret += val;
        return ret;
    }

    chunked_buffer_as_integral_iterator& 
    chunked_buffer_as_integral_iterator::
    operator--() noexcept
    {
        return operator-=(1);
    }

    chunked_buffer_as_integral_iterator  
    chunked_buffer_as_integral_iterator::
    operator--(int) noexcept
    {
        chunked_buffer_as_integral_iterator ret{ *this };
        --ret;
        return ret;
    }

    chunked_buffer_as_integral_iterator& 
    chunked_buffer_as_integral_iterator::
    operator-=(difference_type val) noexcept
    {
        if (val == 0) return *this;
        else if (val < 0) return operator+=(-val);

        auto blk = m_parent->m_cb.read_block(m_cur_blk);

        // reach the begin of current `blk`, and shift to previous block
        while (m_cur_byte - val <= 0)
        {
            val -= m_cur_byte + 1;
            --m_cur_blk;

            /* Make the `blk` refer to previous block
             * and reset `m_cur_byte`
             */
            blk = m_parent->m_cb.read_block(m_cur_blk);
            m_cur_byte = blk.size() - 1;
        }
        m_cur_byte -= val;

        return *this;
    }

    chunked_buffer_as_integral_iterator  
    chunked_buffer_as_integral_iterator::
    operator-(difference_type val) const noexcept
    {
        chunked_buffer_as_integral_iterator ret{ *this };
        ret -= val;
        return ret;
    }

    chunked_buffer_as_integral_iterator::reference 
    chunked_buffer_as_integral_iterator::
    operator*() const noexcept 
    { 
        return m_parent->m_cb.read_block(m_cur_blk)[m_cur_byte]; 
    }

    bool operator==(const chunked_buffer_as_integral_iterator& lhs, 
                    const chunked_buffer_as_integral_iterator& rhs) noexcept
    {
        return (lhs <=> rhs) == ::std::strong_ordering::equal;
    }

    ::std::strong_ordering 
    operator<=>(const chunked_buffer_as_integral_iterator& lhs, 
                const chunked_buffer_as_integral_iterator& rhs) noexcept
    {
        auto blk_cmp = lhs.m_cur_blk <=> rhs.m_cur_blk;
        if (blk_cmp == ::std::strong_ordering::equal)
        {
            return lhs.m_cur_byte <=> rhs.m_cur_byte;
        }
        return blk_cmp;
    }

    chunked_buffer_as_integral_iterator::difference_type 
    operator-(const chunked_buffer_as_integral_iterator& lhs, 
              const chunked_buffer_as_integral_iterator& rhs) noexcept
    {
        auto& parent = *lhs.m_parent;
        auto b = parent.begin(), e = parent.end();
        if (rhs == b && lhs == e)
            return parent.m_cb.size();

        chunked_buffer_as_integral_iterator::difference_type result{};
        chunked_buffer_as_integral_iterator lhs_cpy{ lhs };
        if (lhs_cpy == rhs) return 0;
        else if (lhs_cpy > rhs)
        {
            while (lhs_cpy.m_cur_blk > rhs.m_cur_blk)
            {
                size_t temp = lhs_cpy.m_cur_byte + 1;
                result += temp;
                lhs_cpy -= temp;
            }
            result += lhs_cpy.m_cur_blk - rhs.m_cur_blk;
        }
        return -(rhs - lhs);
    }

    ::std::span<const ::std::byte> 
    chunked_buffer_as_integral_iterator::
    current_block() const noexcept
    { 
        return m_parent->m_cb.read_block(m_cur_blk); 
    }

    chunked_buffer_as_integral_iterator
    chunked_buffer_as_integral::begin() const noexcept
    {
        return { *this, 0, 0 };
    }

    chunked_buffer_as_integral_iterator
    chunked_buffer_as_integral::end() const noexcept
    {
        return { *this, m_cb.blocks_size(), 0 };
    }


}
